package cn.htsyb.synergy.orm.jpa.criterion;

import cn.htsyb.synergy.orm.jpa.model.param.PageParam;
import jakarta.persistence.criteria.*;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * JPA高级查询构建器
 *
 * @author lakeside hubin_yuan.410@hotmail.com
 * @version V1.0.0
 * @date 2022/5/11
 * @since 1.0
 */
@Slf4j
public class DynamicBuilder {
    static final String separator = ".";
    private static final ConversionService conversionService = new DefaultConversionService();

    /**
     * jpa排序器
     *
     * @param OrderArray
     * @return
     */
    public static Sort getSort(Collection<OrderExpress> OrderArray) {
        if (CollectionUtils.isEmpty(OrderArray)) {
            return Sort.unsorted();
        }
        Sort sort = Sort.unsorted();
        if (OrderArray != null) {
            for (OrderExpress orderBy : OrderArray) {
                Sort subSort = Sort.by(Sort.Direction.valueOf(orderBy.getDirection().name()), orderBy.getField());
                sort = sort == null ? subSort : sort.and(subSort);
            }
        }
        return sort;
    }

    /**
     * jpa分页器
     *
     * @param page
     * @param orderArray
     * @return
     */
    public static PageRequest getPageRequest(PageParam page, Collection<OrderExpress> orderArray) {
        Sort orderSort = getSort(orderArray);
        return ObjectUtils.isEmpty(orderSort) ? PageRequest.of(page.getPage(), page.getSize()) : PageRequest.of(page.getPage(), page.getSize(), orderSort);
    }

    /**
     * jpa分页器
     *
     * @param pageNum
     * @param pageSize
     * @param orderBys
     * @return
     */
    public static PageRequest getPageRequest(Integer pageNum, Integer pageSize, Collection<OrderExpress> orderBys) {
        Sort orderSort = getSort(orderBys);
        return ObjectUtils.isEmpty(orderSort) ? PageRequest.of(pageNum, pageSize) : PageRequest.of(pageNum, pageSize, orderSort);
    }

    /**
     * 层级字段路径表达式<b/>
     * 支持子集Join表达式
     *
     * @param field
     * @param root
     * @return
     */
    public static Path getLayerExpression(String field, Root<?> root) {
        Path expression = null;

        try {
            if (field.indexOf(separator) > 0) {
                String[] layerField = StringUtils.split(field, separator);
                for (String subField : layerField) {
                    expression = expression == null ? root.get(subField) : expression.get(subField);
                }
            } else {
                expression = root.get(field);
            }
            return expression;
        } catch (Exception e) {
            log.warn("field {} is not in {}", field, root.getClass());
            return null;
        }

    }

    /**
     * 将查询表达式转化为jpa的Predicate
     *
     * @param expressArray
     * @param root
     * @param query
     * @param cb
     * @return
     */
    public static List<Predicate> getPredicateCollOfExpressColl(Collection<QueryExpress> expressArray, Root<?> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> predicateList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(expressArray)) {
            expressArray.forEach(express -> {
                if (express != null) {
                    predicateList.add(getPredicateOfExpress(express, root, query, cb));
                }
            });
        }
        return predicateList;
    }

    public static Predicate getPredicateOfExpress(@NotNull QueryExpress express, Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        Path expression = DynamicBuilder.getLayerExpression(express.getField(), root);
        if (expression == null) {
            return null;
        }
        Object filterVal = express.getValue();
        if (filterVal != null) {
            Class attributeClass = expression.getJavaType();
            if (!attributeClass.equals(String.class) && filterVal instanceof String && conversionService.canConvert(String.class, attributeClass)) {
                filterVal = conversionService.convert(filterVal, attributeClass);
            }
        }

        switch (express.getOperator()) {
            case EQ:
                return builder.equal(expression, filterVal);
            case NEQ:
                return builder.notEqual(expression, filterVal);
            case LT:
                return builder.lessThan(expression, (Comparable) filterVal);
            case LTE:
                return builder.lessThanOrEqualTo(expression, (Comparable) filterVal);
            case GT:
                return builder.greaterThan(expression, (Comparable) filterVal);
            case GTE:
                return builder.greaterThanOrEqualTo(expression, (Comparable) filterVal);
            case LIKE:
                return builder.like(expression, "%" + filterVal + "%");
            case LIKE_C:
                return builder.like(expression, "" + filterVal);
            case IN:
                if (filterVal == null) {
                    return null;
                } else if (filterVal instanceof Collection) {
                    return expression.in((Collection<?>) filterVal);
                }
            case NOT_IN:
                if (filterVal == null) {
                    return null;
                } else if (filterVal instanceof Collection) {
                    return expression.in((Collection<?>) filterVal).not();
                }
            case IS_NULL:
                return expression.isNull();
            case NOT_NULL:
                return expression.isNotNull();
            case IS_MEMBER:
                return builder.isMember(filterVal, expression);
            case NOT_MEMBER:
                return builder.isNotMember(filterVal, expression);
            default:
                return null;
        }
    }


    private static Predicate andPredicateColl(@NotNull List<Predicate> predicateList, CriteriaBuilder cb) {
        return cb.and(getPredicateArrOfColl(predicateList));
    }

    private static Predicate orPredicateColl(@NotNull List<Predicate> predicateList, CriteriaBuilder cb) {
        return cb.or(getPredicateArrOfColl(predicateList));
    }

    private static Predicate[] getPredicateArrOfColl(@NotNull List<Predicate> predicateList) {
        return predicateList.stream().filter(Objects::nonNull).toArray(Predicate[]::new);
    }


    /**
     * @param andExpressColl
     * @param orExpressColl
     * @param <T>
     * @return
     */
    public static <T> Specification<T> specification(Collection<QueryExpress> andExpressColl, Collection<QueryExpress> orExpressColl) {
        return (root, query, cb) -> {
            query.distinct(true);
            //query.multiselect(root.get(""),root.get(""));

            List<Predicate> searchList = getPredicateCollOfExpressColl(andExpressColl, root, query, cb);
            List<Predicate> filterList = getPredicateCollOfExpressColl(orExpressColl, root, query, cb);
            List<Predicate> predicateList = new ArrayList<>();
            if (!CollectionUtils.isEmpty(searchList)) {
                predicateList.add(andPredicateColl(searchList, cb));
            }
            if (!CollectionUtils.isEmpty(filterList)) {
                predicateList.add(orPredicateColl(searchList, cb));
            }
            return andPredicateColl(predicateList, cb);
        };
    }

}
